package com.davemorrissey.labs.subscaleview;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.*;
import ohos.agp.utils.Color;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Rect;
import ohos.media.image.common.ScaleMode;
import ohos.media.image.common.Size;
import ohos.multimodalinput.event.TouchEvent;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class SubsamplingScaleImageView extends Component implements Component.DrawTask, Component.TouchEventListener {
    private float touchX;
    private float touchY;
    private float touchX2;
    private float touchY2;
    private float touchX3;
    private float touchY3;
    private double ratio = -1;
    private float oMinRatio = 0;
    private float minRatio;
    private float maxRatio = 4;
    private float offsetX;
    private float offsetY;
    private float yuanOffsetX;
    private float yuanOffsetY;
    private long downTime;

    private float touchPcX;
    private float touchPcY;

    private double speedx;
    private double speedy;
    private float mSpeed = 0.005f;

    // test
    private int orientation = 0;

    private long touchlastTime;

    private PixelMap orgPixelMap;
    private int orgWidth;
    private int orgHeight;
    private float samWidth;
    private float samHeight;
    private PixelMap bgPixelMap;
    private PixelMapHolder bgPixelMapHolder;
    private List<PixelMapDo> hopePixelMaps = new ArrayList<>();
    private int hopes = 0;
    private Map<Integer, List<PixelMapDo>> samPixelMaps;
    private int currentSam = 1;

    // 背景图缩放比
    private int bgScale = 1;

    // 横向切割比
    private int widthScale = 1;

    // 纵向切割比
    private int heightScale = 1;

    private Paint paint = new Paint();
    private Paint tilePaint = new Paint();
    private AnimatorValue scrollAnimatorValue;
    private AnimatorValue scaleAnimatorValue;

    // 处理双击事件
    private long firstDown;
    private float firstDownTouchX;
    private float firstDownTouchY;
    private long firstUp;
    private float firstUpTouchX;
    private float firstUpTouchY;
    private long secondDown;
    private float secondDownTouchX;
    private float secondDownTouchY;
    private long secondUp;
    private float secondUpTouchX;
    private float secondUpTouchY;
    private boolean scaleAnimator;

    private boolean isLoad;

    // 加载时候是否有转圈动画
    private boolean loadProgress = true;

    // 是否支持放大缩小
    private boolean zoomEnabled = true;

    // 是否支持平移
    private boolean panEnabled = true;

    // 是否支持双击放大
    private boolean quickScaleEnabled = true;

    // 是否释放不在显示区域的切图，内存优化适合高清图
    private boolean memoryOptimization = false;

    // 双击快速放大比例
    private float quickScale = 2f;

    private int tileBackgroundColor;

    private int doubleTapZoomDuration = 500;

    public SubsamplingScaleImageView(Context context) {
        this(context, null, null);
    }

    public SubsamplingScaleImageView(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
    }

    public SubsamplingScaleImageView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init(attrSet);
    }

    private PixelMap getPixelMapFromResId(int origin) {
        if (origin == -1) {
            return null;
        }
        Optional<PixelMap> optional = ResUtil.getPixelMap(getContext(), origin);
        PixelMap pixelMap = optional.isPresent() ? optional.get() : null;
        return pixelMap;
    }

    private PixelMap getPixelMapFromPath(String path) {
        if (path == null) {
            return null;
        }
        Optional<PixelMap> optional = ResUtil.getPixelMap(getContext(), path);
        PixelMap pixelMap = optional.isPresent() ? optional.get() : null;
        return pixelMap;
    }

    private PixelMap getPixelMapFromUrl(InputStream origin) {
        Optional<PixelMap> optional = ResUtil.getPixelMap(getContext(), origin);
        PixelMap pixelMap = optional.isPresent() ? optional.get() : null;
        return pixelMap;
    }

    private HttpUrlConnectionNetworkImage.HttpCallback httpCallback =
            new HttpUrlConnectionNetworkImage.HttpCallback() {
                @Override
                public void onResponse(InputStream response, int responseLength) throws IOException {
                    orgPixelMap = getPixelMapFromUrl(response);
                    initPixelMap();
                }

                @Override
                public void onFailure(Throwable throwable) {
                }

                @Override
                public void onCancellation() {
                }
            };

    /**
     * set PixelMap with resId
     *
     * @param res the pixelMap resourceId
     */
    public void setResPixelMap(int res) {
        isLoad = true;
        if (loadProgress) {
            invalidate();
        }
        if (orgPixelMap != null) {
            orgPixelMap.release();
            orgPixelMap = null;
        }
        new Thread(
                () -> {
                    orgPixelMap = getPixelMapFromResId(res);
                    initPixelMap();
                })
                .start();
    }

    /**
     * set PixelMap with url
     *
     * @param url the url
     */
    public void setUrlPixelMap(String url) {
        isLoad = true;
        if (loadProgress) {
            invalidate();
        }
        if (orgPixelMap != null) {
            orgPixelMap.release();
            orgPixelMap = null;
        }
        HttpUrlConnectionNetworkImage httpUrlConnectionNetworkImage = new HttpUrlConnectionNetworkImage();
        httpUrlConnectionNetworkImage.fetchSync(url, httpCallback);
    }

    /**
     * set PixelMap with path
     *
     * @param path the pixelMap path
     */
    public void setFilePath(String path) {
        isLoad = true;
        if (loadProgress) {
            invalidate();
        }
        if (orgPixelMap != null) {
            orgPixelMap.release();
            orgPixelMap = null;
        }
        new Thread(
                () -> {
                    orgPixelMap = getPixelMapFromPath(path);
                    initPixelMap();
                })
                .start();
    }

    /**
     * set PixelMap
     *
     * @param pixelMap the pixelMap
     */
    public void setPixelMap(PixelMap pixelMap) {
        isLoad = true;
        if (loadProgress) {
            invalidate();
        }
        if (orgPixelMap != null) {
            orgPixelMap.release();
            orgPixelMap = null;
        }
        orgPixelMap = pixelMap;
        initPixelMap();
    }

    private void initPixelMap() {
        new Thread(
                () -> {
                    while (getWidth() == 0 || getHeight() == 0) {
                        isLoad = true;
                    }
                    if (orgPixelMap == null) {
                        return;
                    }
                    reset();
                    orgWidth = orgPixelMap.getImageInfo().size.width;
                    orgHeight = orgPixelMap.getImageInfo().size.height;
                    minRatio = calculateMinRatio();
                    if (oMinRatio > minRatio && oMinRatio < maxRatio) {
                        minRatio = oMinRatio;
                    }
                    initBgPixelMap();
                    initSamPixelMap();
                    eventHandler.sendEvent(0);
                })
                .start();
    }

    private void initBgPixelMap() {
        if (minRatio > 1) {
            bgPixelMap = orgPixelMap;
            bgPixelMapHolder = new PixelMapHolder(bgPixelMap);
        } else {
            float sr = minRatio;
            while (sr < 1) {
                sr = sr * 2;
                bgScale = bgScale * 2;
            }
            float wr = getWidth() * 1.0f / orgPixelMap.getImageInfo().size.width;
            while (wr < 1) {
                wr = wr * 2;
                widthScale = widthScale * 2;
            }
            float hr = getHeight() * 1.0f / orgPixelMap.getImageInfo().size.height;
            while (hr < 1) {
                hr = hr * 2;
                heightScale = heightScale * 2;
            }
            samWidth = orgWidth * 1.0f / widthScale;
            samHeight = orgHeight * 1.0f / heightScale;

            PixelMap.InitializationOptions initializationOptions =
                    new PixelMap.InitializationOptions();
            initializationOptions.size =
                    new Size(
                            orgPixelMap.getImageInfo().size.width / bgScale,
                            orgPixelMap.getImageInfo().size.height / bgScale);
            initializationOptions.pixelFormat = PixelFormat.ARGB_8888;
            bgPixelMap =
                    PixelMap.create(
                            orgPixelMap,
                            new Rect(
                                    0,
                                    0,
                                    orgPixelMap.getImageInfo().size.width,
                                    orgPixelMap.getImageInfo().size.height),
                            initializationOptions);
            bgPixelMapHolder = new PixelMapHolder(bgPixelMap);
        }
    }

    private void reset() {
        touchX = 0;
        touchY = 0;
        touchX2 = 0;
        touchY2 = 0;
        touchX3 = 0;
        touchY3 = 0;
        ratio = -1;
        minRatio = 0;
        offsetX = 0;
        offsetY = 0;
        yuanOffsetX = 0;
        yuanOffsetY = 0;
        downTime = 0;

        touchPcX = 0;
        touchPcY = 0;

        speedx = 0;
        speedy = 0;
        touchlastTime = 0;
        currentSam = 1;
        bgScale = 1;
        widthScale = 1;
        heightScale = 1;
    }

    private float calculateMinRatio() {
        if (orgPixelMap != null) {
            return Math.min(getWidth() * 1.0f / orgPixelMap.getImageInfo().size.width, getHeight() * 1.0f / orgPixelMap.getImageInfo().size.height);
        } else {
            return minRatio;
        }

    }

    private EventHandler eventHandler =
            new EventHandler(EventRunner.getMainEventRunner()) {
                @Override
                protected void processEvent(InnerEvent event) {
                    isLoad = false;
                    invalidate();
                }
            };

    public void setEagerLoadingEnabled(boolean eagerLoadingEnabled) {
        this.loadProgress = eagerLoadingEnabled;
    }

    public void setZoomEnabled(boolean zoomEnabled) {
        this.zoomEnabled = zoomEnabled;
        if (orgPixelMap != null) {
            invalidate();
        }
    }

    public void setPanEnabled(boolean panEnabled) {
        this.panEnabled = panEnabled;
        if (orgPixelMap != null && !panEnabled) {
            offsetX = (float) (getWidth() - orgPixelMap.getImageInfo().size.width * ratio) / 2;
            offsetY = (float) (getHeight() - orgPixelMap.getImageInfo().size.height * ratio) / 2;
            invalidate();
        }
    }

    public void setDoubleTapZoomScale(float quickScale) {
        if (quickScale > maxRatio && orgPixelMap != null) {
            this.quickScale = maxRatio;
        } else if (quickScale < minRatio && orgPixelMap != null) {
            this.quickScale = minRatio;
        } else {
            this.quickScale = quickScale;
        }
    }

    public void setMemoryOptimization(boolean memoryOptimization) {
        if (this.memoryOptimization != memoryOptimization) {
            this.memoryOptimization = memoryOptimization;
            invalidate();
        }
    }

    public boolean isQuickScaleEnabled() {
        return quickScaleEnabled;
    }

    public boolean isZoomEnabled() {
        return zoomEnabled;
    }

    public boolean isPanEnabled() {
        return panEnabled;
    }

    public void setQuickScaleEnabled(boolean quickScaleEnabled) {
        this.quickScaleEnabled = quickScaleEnabled;
    }

    public boolean isMemoryOptimization() {
        return memoryOptimization;
    }

    public void setMaxRatio(float scale) {
        this.maxRatio = scale;
        if (orgPixelMap != null && ratio > maxRatio) {
            ratio = maxRatio;
            invalidate();
        }
    }

    public void setRatio(float scale) {
        this.ratio = scale;
        if (orgPixelMap != null) {
            invalidate();
        }
    }

    public void setMinScale(float scale) {
        this.oMinRatio = scale;
        if (oMinRatio > minRatio && oMinRatio < maxRatio) {
            minRatio = oMinRatio;
            invalidate();
        }
    }

    public float getMinScale() {
        return oMinRatio;
    }

    public float getMaxScale() {
        return maxRatio;
    }

    public final void resetScaleAndCenter() {
        ratio = minRatio;
        invalidate();
    }

    public final void setTileBackgroundColor(int color) {
        tileBackgroundColor = color;
        invalidate();
    }

    public final boolean isReady() {
        return !isLoad && orgPixelMap != null && !orgPixelMap.isReleased();
    }

    public final boolean isImageLoaded() {
        return isLoad;
    }

    public final void setDoubleTapZoomDuration(int durationMs) {
        this.doubleTapZoomDuration = Math.max(0, durationMs);
    }

    private float getTouchX(TouchEvent touchEvent, int index) {
        float getX = 0;
        if (touchEvent.getPointerCount() > index) {
            int[] xy = getLocationOnScreen();
            if (xy != null && xy.length == 2) {
                getX = touchEvent.getPointerScreenPosition(index).getX() - xy[0];
            } else {
                getX = touchEvent.getPointerPosition(index).getX();
            }
        }
        return getX;
    }

    private float getTouchY(TouchEvent touchEvent, int index) {
        float getY = 0;
        if (touchEvent.getPointerCount() > index) {
            int[] xy = getLocationOnScreen();
            if (xy != null && xy.length == 2) {
                getY = touchEvent.getPointerScreenPosition(index).getY() - xy[1];
            } else {
                getY = touchEvent.getPointerPosition(index).getY();
            }
        }
        return getY;
    }

    private void init(AttrSet attrSet) {
        panEnabled = AttrUtils.getBooleanFromAttr(attrSet, "panEnabled", true);
        zoomEnabled = AttrUtils.getBooleanFromAttr(attrSet, "zoomEnabled", true);
        quickScaleEnabled = AttrUtils.getBooleanFromAttr(attrSet, "quickScaleEnabled", true);
        maxRatio = AttrUtils.getFloatFromAttr(attrSet, "maxRatio", 4f);
        loadProgress = AttrUtils.getBooleanFromAttr(attrSet, "loadProgress", true);
        tileBackgroundColor = AttrUtils.getColorFromAttr(attrSet, "tileBackgroundColor", Color.BLACK.getValue());
        memoryOptimization = AttrUtils.getBooleanFromAttr(attrSet, "memoryOptimization", false);

        scrollAnimatorValue = new AnimatorValue();
        scrollAnimatorValue.setDuration(20000);
        scrollAnimatorValue.setCurveType(Animator.CurveType.LINEAR);
        paint.setAntiAlias(true);
        paint.setFilterBitmap(true);
        paint.setDither(true);
        setTouchEventListener(this);
        addDrawTask(this);
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        if (scrollAnimatorValue != null && scrollAnimatorValue.isRunning()) {
            scrollAnimatorValue.stop();
        }
        if (scaleAnimator) {
            return true;
        }
        if (orgPixelMap == null) {
            return false;
        }
        if (touchEvent.getPointerCount() >= 3) {
            if (touchEvent.getAction() == TouchEvent.OTHER_POINT_UP
                    || touchEvent.getAction() == TouchEvent.OTHER_POINT_UP) {
                doThreeTouchUp(touchEvent);
            }
            return true;
        }
        if (touchEvent.getPointerCount() >= 2) {
            if (touchEvent.getAction() == TouchEvent.OTHER_POINT_DOWN
                    || touchEvent.getAction() == TouchEvent.PRIMARY_POINT_DOWN) {
                doTwoTouchDown(touchEvent);
            } else if (touchEvent.getAction() == TouchEvent.POINT_MOVE) {
                doTwoTouchMove(touchEvent);
            } else if (touchEvent.getAction() == TouchEvent.OTHER_POINT_UP
                    || touchEvent.getAction() == TouchEvent.PRIMARY_POINT_UP) {
                doTwoTouchUp(touchEvent);
            }
            return true;
        } else if (touchEvent.getPointerCount() == 1) {
            if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_DOWN) {
                doOneTouchDown(touchEvent);
            } else if (touchEvent.getAction() == TouchEvent.POINT_MOVE) {
                doOneTouchMove(touchEvent);
                invalidate();
            } else if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_UP) {
                doOneTouchUp(touchEvent);
            }
        }
        return true;
    }

    private void doThreeTouchUp(TouchEvent touchEvent) {
        if (touchEvent.getIndex() == 0) {
            touchX2 = getTouchX(touchEvent, 2);
            touchY2 = getTouchY(touchEvent, 2);
            touchX = getTouchX(touchEvent, 1);
            touchY = getTouchY(touchEvent, 1);
            touchPcX =
                    (float)
                            ((((getTouchX(touchEvent, 0) + getTouchX(touchEvent, 1)) / 2) - offsetX)
                                    / ratio);
            touchPcY =
                    (float)
                            ((((getTouchY(touchEvent, 0) + getTouchY(touchEvent, 1)) / 2) - offsetY)
                                    / ratio);
        } else if (touchEvent.getIndex() == 1) {
            touchX2 = getTouchX(touchEvent, 2);
            touchY2 = getTouchY(touchEvent, 2);
            touchX = getTouchX(touchEvent, 0);
            touchY = getTouchY(touchEvent, 0);
            touchPcX =
                    (float)
                            ((((getTouchX(touchEvent, 0) + getTouchX(touchEvent, 1)) / 2) - offsetX)
                                    / ratio);
            touchPcY =
                    (float)
                            ((((getTouchY(touchEvent, 0) + getTouchY(touchEvent, 1)) / 2) - offsetY)
                                    / ratio);
        }
    }

    private void doTwoTouchDown(TouchEvent touchEvent) {
        touchX2 = getTouchX(touchEvent, 1);
        touchY2 = getTouchY(touchEvent, 1);
        touchX = getTouchX(touchEvent, 0);
        touchY = getTouchY(touchEvent, 0);
        touchPcX =
                (float)
                        ((((getTouchX(touchEvent, 0) + getTouchX(touchEvent, 1)) / 2) - offsetX)
                                / ratio);
        touchPcY =
                (float)
                        ((((getTouchY(touchEvent, 0) + getTouchY(touchEvent, 1)) / 2) - offsetY)
                                / ratio);
    }

    private void doTwoTouchMove(TouchEvent touchEvent) {
        double yuanRatio = ratio;
        if (zoomEnabled) {
            ratio =
                    ratio
                            * Math.sqrt(
                            (getTouchX(touchEvent, 0) - getTouchX(touchEvent, 1))
                                    * (getTouchX(touchEvent, 0)
                                    - getTouchX(touchEvent, 1))
                                    + (getTouchY(touchEvent, 0) - getTouchY(touchEvent, 1))
                                    * (getTouchY(touchEvent, 0)
                                    - getTouchY(touchEvent, 1)))
                            / Math.sqrt(
                            (touchX - touchX2) * (touchX - touchX2)
                                    + (touchY - touchY2) * (touchY - touchY2));
            if (ratio > maxRatio) {
                ratio = maxRatio;
            }
            if (ratio < minRatio) {
                ratio = minRatio;
            }
        }
        offsetX +=
                (getTouchX(touchEvent, 0) + getTouchX(touchEvent, 1)) / 2 - (touchX + touchX2) / 2;
        offsetX -= touchPcX * ratio - touchPcX * yuanRatio;

        offsetY +=
                (getTouchY(touchEvent, 0) + getTouchY(touchEvent, 1)) / 2 - (touchY + touchY2) / 2;
        offsetY -= touchPcY * ratio - touchPcY * yuanRatio;

        touchX2 = getTouchX(touchEvent, 1);
        touchY2 = getTouchY(touchEvent, 1);
        touchX = getTouchX(touchEvent, 0);
        touchY = getTouchY(touchEvent, 0);
        invalidate();
    }

    private void doTwoTouchUp(TouchEvent touchEvent) {
        if (touchEvent.getIndex() == 0 && touchEvent.getPointerCount() == 2) {
            touchX = getTouchX(touchEvent, 1);
            touchY = getTouchY(touchEvent, 1);
        }
    }

    private void doOneTouchDown(TouchEvent touchEvent) {
        touchX = getTouchX(touchEvent, 0);
        touchY = getTouchY(touchEvent, 0);
        speedx = 0;
        speedy = 0;

        long thisTime = System.currentTimeMillis();
        if (thisTime - firstDown > 600) {
            resetDoubleTouch();
            firstDown = thisTime;
            firstDownTouchX = touchX;
            firstDownTouchY = touchY;
        } else if (secondDown == 0
                && thisTime - firstUp < 300
                && Math.abs(touchX - firstUpTouchX) < dp2px(30)
                && Math.abs(touchY - firstUpTouchY) < dp2px(30)) {
            secondDown = thisTime;
            secondDownTouchX = touchX;
            secondDownTouchY = touchY;
        } else {
            resetDoubleTouch();
            firstDown = thisTime;
            firstDownTouchX = touchX;
            firstDownTouchY = touchY;
        }
    }

    private void doOneTouchMove(TouchEvent touchEvent) {
        long thisTime = System.currentTimeMillis();
        speedx = (getTouchX(touchEvent, 0) - touchX) / (thisTime - touchlastTime);
        speedy = (getTouchY(touchEvent, 0) - touchY) / (thisTime - touchlastTime);
        offsetX += getTouchX(touchEvent, 0) - touchX;
        offsetY += getTouchY(touchEvent, 0) - touchY;
        touchX = getTouchX(touchEvent, 0);
        touchY = getTouchY(touchEvent, 0);
        touchlastTime = System.currentTimeMillis();
    }

    private void doOneTouchUp(TouchEvent touchEvent) {
        touchX = getTouchX(touchEvent, 0);
        touchY = getTouchY(touchEvent, 0);
        long thisTime = System.currentTimeMillis();
        if (firstUp == 0
                && thisTime - firstDown < 300
                && Math.abs(touchX - firstDownTouchX) < dp2px(30)
                && Math.abs(touchY - firstDownTouchY) < dp2px(30)) {
            firstUp = thisTime;
            firstUpTouchX = touchX;
            firstUpTouchY = touchY;
        } else if (secondUp == 0
                && thisTime - secondDown < 300
                && Math.abs(touchX - secondDownTouchX) < dp2px(30)
                && Math.abs(touchY - secondDownTouchY) < dp2px(30)) {
            secondUp = thisTime;
            secondUpTouchX = touchX;
            secondUpTouchY = touchY;
            if (orgPixelMap != null && panEnabled && zoomEnabled && quickScaleEnabled) {
                calScaleAnimator(touchX, touchY, ratio);
            }
            resetDoubleTouch();
        } else {
            resetDoubleTouch();
            startScrollAnimator();
        }
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        tilePaint.setColor(new Color(tileBackgroundColor));
        canvas.drawRect(new RectFloat(0, 0, getWidth(), getHeight()), tilePaint);
        if (orgPixelMap != null && bgPixelMap != null && !isLoad) {
            if (mAnimatedDrawable != null) {
                mAnimatedDrawable.stop();
                mAnimatedDrawable = null;
            }
            if (ratio < minRatio) {
                ratio = minRatio;
            }
            if (!scaleAnimator) {
                checkZoom();
            }
            RectFloat rect =
                    new RectFloat(
                            (float) (-offsetX / ratio),
                            (float) (-offsetY / ratio),
                            (float)
                                    (orgPixelMap.getImageInfo().size.width
                                            - ((orgPixelMap.getImageInfo().size.width) * ratio
                                            + offsetX
                                            - getWidth())
                                            / ratio),
                            (float)
                                    (orgPixelMap.getImageInfo().size.height
                                            - ((orgPixelMap.getImageInfo().size.height) * ratio
                                            + offsetY
                                            - getHeight())
                                            / ratio));

            calCurrentSam();
            if (currentSam < bgScale && checkHashMap(rect) && !scaleAnimator) {
                drawSamPixelMap(rect, currentSam, canvas, ratio);
            } else if (bgPixelMap != null) {
                rect =
                        new RectFloat(
                                rect.left / bgScale,
                                rect.top / bgScale,
                                rect.right / bgScale,
                                rect.bottom / bgScale);
                RectFloat rectf = new RectFloat(0, 0, getWidth(), getHeight());
                if (bgPixelMapHolder == null) {
                    bgPixelMapHolder = new PixelMapHolder(bgPixelMap);
                }
                canvas.drawPixelMapHolderRect(bgPixelMapHolder, rect, rectf, paint);
            }
        } else if (isLoad && loadProgress) {
            drawIndeterminateProgress(canvas);
        }
    }

    private void resetDoubleTouch() {
        firstDown = 0;
        firstUp = 0;
        secondUp = 0;
        secondDown = 0;
    }

    private void calCurrentSam() {
        int ratioOrg = 1;
        while (ratioOrg * ratio < 1) {
            ratioOrg = ratioOrg * 2;
        }
        currentSam = ratioOrg;
    }

    private void initSamPixelMap() {
        samPixelMaps = new HashMap<>();
        for (int i = 1; i <= bgScale; i = i * 2) {
            List<PixelMapDo> pixelMapDos = new ArrayList<>();
            for (int w = 0; w < widthScale; w++) {
                for (int h = 0; h < heightScale; h++) {
                    PixelMapDo pixelMapDo = new PixelMapDo();
                    pixelMapDo.sam = i;
                    pixelMapDo.width = w;
                    pixelMapDo.height = h;
                    pixelMapDo.rect =
                            new RectFloat(samWidth * w, samHeight * h, samWidth * (w + 1), samHeight * (h + 1));
                    pixelMapDos.add(pixelMapDo);
                }
            }
            samPixelMaps.put(i, pixelMapDos);
        }
    }

    private void drawSamPixelMap(RectFloat rect, int sam, Canvas canvas, double ratio) {
        if (samPixelMaps != null) {
            List<PixelMapDo> pixelMapDos = samPixelMaps.get(sam);
            for (int i = 0; i < pixelMapDos.size(); i++) {
                PixelMapDo pixelMapDo = pixelMapDos.get(i);
                if (pixelMapDo != null && pixelMapDo.inVisible) {
                    if (pixelMapDo.isFinish && pixelMapDo.pixelMap != null && !pixelMapDo.pixelMap.isReleased()) {
                        RectFloat samSrc =
                                new RectFloat(
                                        0,
                                        0,
                                        pixelMapDo.pixelMap.getImageInfo().size.width,
                                        pixelMapDo.pixelMap.getImageInfo().size.height);
                        RectFloat screen =
                                new RectFloat(
                                        (int) ((pixelMapDo.rect.left - rect.left) * ratio),
                                        (int) ((pixelMapDo.rect.top - rect.top) * ratio),
                                        (int) ((pixelMapDo.rect.right - rect.left) * ratio),
                                        (int) ((pixelMapDo.rect.bottom - rect.top) * ratio));
                        if (pixelMapDo.pixelMapHolder == null) {
                            pixelMapDo.pixelMapHolder = new PixelMapHolder(pixelMapDo.pixelMap);
                        }
                        canvas.drawPixelMapHolderRect(pixelMapDo.pixelMapHolder, samSrc, screen, paint);

                    } else {
                        PixelMapDo samBigpixelMapDo = hopeSamBigPixmap(i, sam);
                        if (samBigpixelMapDo != null) {
                            RectFloat samSrc =
                                    new RectFloat(
                                            0,
                                            0,
                                            samBigpixelMapDo.pixelMap.getImageInfo().size.width,
                                            samBigpixelMapDo.pixelMap.getImageInfo().size.height);
                            RectFloat screen =
                                    new RectFloat(
                                            (int) ((pixelMapDo.rect.left - rect.left) * ratio),
                                            (int) ((pixelMapDo.rect.top - rect.top) * ratio),
                                            (int) ((pixelMapDo.rect.right - rect.left) * ratio),
                                            (int) ((pixelMapDo.rect.bottom - rect.top) * ratio));
                            if (samBigpixelMapDo.pixelMapHolder == null) {
                                samBigpixelMapDo.pixelMapHolder = new PixelMapHolder(samBigpixelMapDo.pixelMap);
                            }
                            canvas.drawPixelMapHolderRect(
                                    samBigpixelMapDo.pixelMapHolder, samSrc, screen, paint);
                        }
                    }
                }
            }
        }
    }

    private boolean checkZoom() {
        boolean moveToStrock = false;
        int width = getWidth();
        int height = getHeight();
        if (panEnabled) {
            if (offsetX < width - orgPixelMap.getImageInfo().size.width * ratio) {
                offsetX = (float) (width - orgPixelMap.getImageInfo().size.width * ratio);
                moveToStrock = true;
            }
            if (offsetY < height - orgPixelMap.getImageInfo().size.height * ratio) {
                offsetY = (float) (height - orgPixelMap.getImageInfo().size.height * ratio);
                moveToStrock = true;
            }
            if (offsetX > 0) {
                offsetX = 0;
                moveToStrock = true;
            }
            if (offsetY > 0) {
                offsetY = 0;
                moveToStrock = true;
            }
            if (getWidth() > orgPixelMap.getImageInfo().size.width * ratio) {
                offsetX = (float) (width - orgPixelMap.getImageInfo().size.width * ratio) / 2;
                moveToStrock = true;
            }
            if (height > orgPixelMap.getImageInfo().size.height * ratio) {
                offsetY = (float) (height - orgPixelMap.getImageInfo().size.height * ratio) / 2;
                moveToStrock = true;
            }
        } else {
            offsetX = (float) (width - orgPixelMap.getImageInfo().size.width * ratio) / 2;
            offsetY = (float) (height - orgPixelMap.getImageInfo().size.height * ratio) / 2;
            moveToStrock = true;
        }
        return moveToStrock;
    }

    private boolean isXRect(RectFloat r1, RectFloat r2) {
        if (Math.max(r1.left, r2.left) >= Math.min(r1.right, r2.right)
                || Math.max(r1.top, r2.top) >= Math.min(r1.bottom, r2.bottom)) {
            return false;
        }
        return true;
    }

    private RectFloat getXRect(RectFloat r1, RectFloat r2) {
        if (isXRect(r1, r2)) {
            return new RectFloat(
                    Math.max(r1.left, r2.left),
                    Math.max(r1.top, r2.top),
                    Math.min(r1.right, r2.right),
                    Math.min(r1.bottom, r2.bottom));
        }
        return new RectFloat();
    }

    private boolean checkHashMap(RectFloat rect) {
        boolean result = true;
        if (samPixelMaps == null) {
            return false;
        }
        List<PixelMapDo> pixelMapDos = samPixelMaps.get(currentSam);
        if (pixelMapDos == null) {
            return false;
        }
        for (int sam = 1; sam < bgScale; sam = sam * 2) {
            for (int i = 0; i < pixelMapDos.size(); i++) {
                PixelMapDo pixelMapDo = pixelMapDos.get(i);
                if (isXRect(rect, pixelMapDo.rect)) {
                    pixelMapDo.inVisible = true;
                    if (sam == currentSam) {
                        if (pixelMapDo == null || pixelMapDo.pixelMap == null || pixelMapDo.pixelMap.isReleased()) {
                            pixelMapDo.needHope = true;
                            doHopePixelMaps(pixelMapDo);
                            if (hopeSamBigPixmap(i, currentSam) == null) {
                                result = false;
                            }
                        }
                    }
                } else {
                    pixelMapDo.inVisible = false;
                }
            }
        }
        return result;
    }

    private PixelMapDo hopeSamBigPixmap(int index, int sam) {
        while (sam < bgScale) {
            sam = sam * 2;
            if (samPixelMaps != null) {
                List<PixelMapDo> pixelMapDos = samPixelMaps.get(sam);
                if (pixelMapDos != null && pixelMapDos.size() > index) {
                    PixelMapDo pixelMapDo = pixelMapDos.get(index);
                    if (pixelMapDo != null
                            && pixelMapDo.isFinish
                            && pixelMapDo.pixelMap != null
                            && !pixelMapDo.pixelMap.isReleased()) {
                        return pixelMapDo;
                    }
                }
            }
        }
        return null;
    }

    private Thread hopeThread;

    private void doHopePixelMaps(PixelMapDo pixelMapDo) {
        if (hopePixelMaps == null) {
            hopePixelMaps = new ArrayList<>();
        }
        synchronized (hopePixelMaps) {
            if (!hopePixelMaps.contains(pixelMapDo)) {
                hopePixelMaps.add(pixelMapDo);
            }
        }

        if (hopeThread == null || !hopeThread.isAlive()) {
            hopeThread =
                    new Thread(
                            () -> {
                                while (true) {
                                    boolean needStop = true;
                                    if (hopePixelMaps != null) {
                                        for (int i = 0; i < hopePixelMaps.size(); i++) {
                                            if (pixelMapDo != null
                                                    && pixelMapDo.pixelMap == null
                                                    && !pixelMapDo.isFinish
                                                    && pixelMapDo.needHope) {
                                                needStop = false;
                                            }
                                        }
                                    }
                                    if (hopePixelMaps == null || needStop) {
                                        break;
                                    }
                                    createHopePixelMaps();
                                }
                            });
            hopeThread.start();
        }
    }

    private void createHopePixelMaps() {
        for (int i = 0; i < hopePixelMaps.size(); i++) {
            PixelMapDo pixelMapDo1 = hopePixelMaps.get(i);
            if (pixelMapDo1 != null
                    && pixelMapDo1.pixelMap == null
                    && !pixelMapDo1.isFinish
                    && pixelMapDo1.needHope) {
                PixelMap.InitializationOptions initializationOptions =
                        new PixelMap.InitializationOptions();
                initializationOptions.size = new Size((int) samWidth, (int) samHeight);
                initializationOptions.scaleMode = ScaleMode.CENTER_CROP;
                initializationOptions.pixelFormat = PixelFormat.RGB_565;
                Rect rect =
                        new Rect(
                                (int) pixelMapDo1.rect.left,
                                (int) pixelMapDo1.rect.top,
                                (int) samWidth,
                                (int) samHeight);
                pixelMapDo1.pixelMap =
                        PixelMap.create(orgPixelMap, rect, initializationOptions);
                pixelMapDo1.pixelMapHolder = new PixelMapHolder(pixelMapDo1.pixelMap);
                if (pixelMapDo1.pixelMap != null
                        && pixelMapDo1.pixelMap.getImageInfo().size.width > 0
                        && pixelMapDo1.pixelMap.getImageInfo().size.height > 0) {
                    pixelMapDo1.isFinish = true;
                    pixelMapDo1.needHope = false;
                    hopes++;
                }
            } else if (memoryOptimization && pixelMapDo1.pixelMap != null
                    && !pixelMapDo1.inVisible
                    && pixelMapDo1.isFinish
                    && !pixelMapDo1.pixelMap.isReleased()) {
                pixelMapDo1.pixelMapHolder = null;
                pixelMapDo1.pixelMap.release();
                pixelMapDo1.pixelMap = null;
                pixelMapDo1.isFinish = false;
            }
        }
    }

    private class PixelMapDo {
        boolean inVisible;
        int sam;
        int width;
        int height;
        RectFloat rect;
        PixelMap pixelMap;
        PixelMapHolder pixelMapHolder;
        boolean isFinish;
        boolean needHope;
    }

    private long lastAnimatorTime;
    private double lastAnimatorSpx;
    private double lastAnimatorSpy;

    private int dp2px(float dp) {
        return (int) (getResourceManager().getDeviceCapability().screenDensity / 160 * dp);
    }

    private CircularAnimatedDrawable mAnimatedDrawable;

    private void drawIndeterminateProgress(Canvas canvas) {
        if (mAnimatedDrawable == null) {
            int offset = (getWidth() - getHeight()) / 2;
            mAnimatedDrawable = new CircularAnimatedDrawable(0xffff5454, dp2px(4));
            int left = getWidth() / 2 - dp2px(20);
            int right = getWidth() / 2 + dp2px(20);
            int bottom = getHeight() / 2 + dp2px(20);
            int top = getHeight() / 2 - dp2px(20);
            mAnimatedDrawable.onBoundsChange(new ohos.agp.utils.Rect(left, top, right, bottom));
            mAnimatedDrawable.setComponent(this);
            mAnimatedDrawable.start();
            mAnimatedDrawable.drawToCanvas(canvas);
        } else {
            mAnimatedDrawable.drawToCanvas(canvas);
        }
    }

    private void calScaleAnimator(float touchX, float touchY, double ratio) {
        if (1.8f <= ratio) {
            float toX = 0;
            float toY = 0;
            if (getWidth() > orgPixelMap.getImageInfo().size.width * minRatio) {
                toX = (float) (getWidth() - orgPixelMap.getImageInfo().size.width * minRatio) / 2;
            }
            if (getHeight() > orgPixelMap.getImageInfo().size.height * minRatio) {
                toY = (float) (getHeight() - orgPixelMap.getImageInfo().size.height * minRatio) / 2;
            }
            startScaleAnimator(offsetX, toX, offsetY, toY, ratio, minRatio, touchX, touchY);
        } else {
            float toX = (float) (touchX - (touchX - offsetX) / ratio * quickScale);
            float toY = (float) (touchY - (touchY - offsetY) / ratio * quickScale);

            if (toX < getWidth() - orgPixelMap.getImageInfo().size.width * quickScale) {
                toX = (float) (getWidth() - orgPixelMap.getImageInfo().size.width * quickScale);
            }
            if (toY < getHeight() - orgPixelMap.getImageInfo().size.height * quickScale) {
                toY = (float) (getHeight() - orgPixelMap.getImageInfo().size.height * quickScale);
            }
            if (toX > 0) {
                toX = 0;
            }
            if (toY > 0) {
                toY = 0;
            }
            if (getWidth() > orgPixelMap.getImageInfo().size.width * quickScale) {
                toX = (float) (getWidth() - orgPixelMap.getImageInfo().size.width * quickScale) / 2;
            }
            if (getHeight() > orgPixelMap.getImageInfo().size.height * quickScale) {
                toY = (float) (getHeight() - orgPixelMap.getImageInfo().size.height * quickScale) / 2;
            }

            startScaleAnimator(offsetX, toX, offsetY, toY, ratio, 2f, touchX, touchY);
        }
    }

    private void startScaleAnimator(
            float fromX,
            float toX,
            float fromY,
            float toY,
            double fromRatio,
            double toRatio,
            float touchX,
            float touchY) {
        if (scaleAnimatorValue == null) {
            scaleAnimatorValue = new AnimatorValue();
            scaleAnimatorValue.setDuration(doubleTapZoomDuration);
            scaleAnimatorValue.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE);
        }
        scaleAnimatorValue.setValueUpdateListener(
                (animatorValue, v) -> {
                    offsetX = fromX + (toX - fromX) * v;
                    offsetY = fromY + (toY - fromY) * v;
                    ratio = fromRatio + (toRatio - fromRatio) * v;
                    getContext()
                            .getUITaskDispatcher()
                            .asyncDispatch(
                                    () -> invalidate());
                });
        scaleAnimatorValue.setStateChangedListener(
                new Animator.StateChangedListener() {
                    @Override
                    public void onStart(Animator animator) {
                    }

                    @Override
                    public void onStop(Animator animator) {
                    }

                    @Override
                    public void onCancel(Animator animator) {
                    }

                    @Override
                    public void onEnd(Animator animator) {
                        scaleAnimator = false;
                        getContext()
                                .getUITaskDispatcher()
                                .asyncDispatch(
                                        () -> invalidate());
                    }

                    @Override
                    public void onPause(Animator animator) {
                    }

                    @Override
                    public void onResume(Animator animator) {
                    }
                });
        scaleAnimator = true;
        scaleAnimatorValue.start();
    }

    private void startScrollAnimator() {
        if (scrollAnimatorValue == null) {
            scrollAnimatorValue = new AnimatorValue();
            scrollAnimatorValue.setDuration(20000);
            scrollAnimatorValue.setCurveType(Animator.CurveType.LINEAR);
        }
        lastAnimatorTime = touchlastTime;
        lastAnimatorSpx = speedx;
        lastAnimatorSpy = speedy;
        if (lastAnimatorSpx == 0 && lastAnimatorSpy == 0) {
            return;
        }

        scrollAnimatorValue.setValueUpdateListener(
                (animatorValue, v) -> {
                    long time = System.currentTimeMillis();
                    double speed = Math.sqrt(speedx * speedx + speedy * speedy);
                    double spa = speed - (time - touchlastTime) * mSpeed;
                    double spx = 0;
                    double spy = 0;
                    if (speedx == 0) {
                        spy = spa;
                    } else if (speedy == 0) {
                        spx = spa;
                    } else {
                        spx = Math.sqrt(spa * spa / (1 + (speedy * speedy) / (speedx * speedx)));
                        if (speedx < 0) {
                            spx = -spx;
                        }
                        spy = Math.sqrt(spa * spa / (1 + (speedx * speedx) / (speedy * speedy)));
                        if (speedy < 0) {
                            spy = -spy;
                        }
                    }
                    if (spa <= 0) {
                        spa = 0;
                        spx = 0;
                        spy = 0;
                        scrollAnimatorValue.stop();
                    }
                    offsetX += (time - lastAnimatorTime) * (lastAnimatorSpx + spx) / 2;
                    offsetY += (time - lastAnimatorTime) * (lastAnimatorSpy + spy) / 2;
                    lastAnimatorTime = time;
                    lastAnimatorSpx = spx;
                    lastAnimatorSpy = spy;
                    getContext()
                            .getUITaskDispatcher()
                            .asyncDispatch(
                                    () -> invalidate());
                });
        scrollAnimatorValue.start();
    }
}
